/*
 * @Author: ipk518 121206530@qq.com
 * @Date: 2023-07-21 16:34:59
 * @LastEditors: ipk518 18163976442@163.com
 * @LastEditTime: 2023-08-19 17:09:28
 * @FilePath: \WingsUpdater-main\src\Wings\Wings.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
#include "Wings.h"
#include "myLog.h"
#include "Description.h"
#include <map>



using namespace std;


unsigned short g_usMajor=1;
unsigned short g_usMinor=0;
unsigned short g_usPatch=0;
static char g_ucMountPoint[100];//挂载节点
static char g_ucMountCmd[100];
static char g_ucUmount[100];
// const char* m_mkdir = "mkdir -p /mnt/ram";
// const char* m_mount = "mount -t ramfs -o size=50m ramfs /mnt/ram";
// const char* m_umount = "umount ramfs";

// const char* m_mount = "mount -t ramfs -o size=50m ramfs /home/ipk518/ts/ram";
// const char* m_umount = "umount /home/ipk518/ts/ram";

static int g_usMaxSize = 0;
static char g_ucFilePath[100];
static char g_ucConfName[100];


#define MD5_NAME "/Md5.txt"
#define MD5_COPYNAME "/Md5to.txt"
#define TAR_NAME "/Package.tar.bz2"
#define DESCRIPTION_NAME "/Description.ini"
#define EXTENSION_NAME ".tar.bz2"

// #define PONG_NUM    0
// #define PING_NUM    1


// static map<int, string> g_updateInfoMap = {
//     {0,""}

// }

namespace Wings 
{


void wingInit(void)
{
    memset(g_ucConfName,'\0',sizeof(g_ucConfName));
    memset(g_ucFilePath,'\0',sizeof(g_ucFilePath));
    memset(g_ucUmount,'\0',sizeof(g_ucUmount));
    memset(g_ucMountCmd,'\0',sizeof(g_ucMountCmd));
    memset(g_ucMountPoint,'\0',sizeof(g_ucMountPoint));
}

const char *errorInfo(int32_t code)
{
// #include<unistd.h>
// int symlink(const char*actualpath,const char *sympath);
//  int symlinkat(const char *actualpath,int fd,const char *sympath);
//                            两个函数返回值：若成功，返回0;若出错，返回-1
// ssize_t readlink(const char* restrict pathname,char *restrict buf,size_t bufsize);
// ssize_t readlinkat(int fd,const char* restrict pathname,char *restrict buf,size_t bufsize);
//                            两个函数返回值：若成功，返回读取的字节数；若出错，返回-1
    return "test";
}


static void updateApp(const char *new_path);
static void updateDriver(const char *new_path);
static void updateOther(const char *new_path);

/**
   * 增强的system函数，能够返回system调用的输出
   *
   * @param[in] cmdstring 调用外部程序或脚本的命令串
   * @param[out] buf 返回外部命令的结果的缓冲区
   * @param[in] len 缓冲区buf的长度
   *
   * @return 0: 成功; -1: 失败 
   */
static int mySystem(const char* cmdstring, char* buf, int len)
{
    char cmd_string[256];
    char buffer[256] = { '\0' };
    int res=-1,tmp=0;
    char *ptr = tmpnam(NULL);//返回临时文件名
    qlog_d("tmpnam file: %s", ptr);
    if(ptr != NULL){
        sprintf(cmd_string, "%s > %s", cmdstring, ptr);
        res = system(cmd_string);

        FILE* file = fopen(ptr,"rb");
        if(file == NULL){
            qlog_e("fopen %s failed",ptr);
            return -1;
        }
        //读取文件，并返回所读取char型数据元素的个数；
        res = fread(buffer, sizeof(buffer), 1, file);
        if(res < 0){
            qlog_e("tmpnam data error!");
            return -1;
        }
        
        qlog_i("%s of value is:%s", ptr,buffer);
        tmp = (unsigned char)(strchr(buffer, '\n') - buffer);
        char *data = (tmp > 0)?strndup(buffer, tmp):strdup(buffer);
        strcpy(buf,data);
        
        memset(cmd_string,'\0',sizeof(cmd_string));
        sprintf(cmd_string, "rm -rfd %s", ptr);
        res = system(cmd_string);
    }else{
        qlog_e("tmpnam failed!");
    }

    return res;
}

static int getMd5Data(const char *path,char* data)
{
    if(path == NULL || data == NULL){
        return -1;
    } 
    char buffer[256] = {'\0'};
    int res = 0;
    FILE* file = fopen(path,"rb");
    if(file == NULL){
        qlog_e("fopen %s failed",path);
        return 1;
    }
    //读取文件，并返回所读取char型数据元素的个数；
    res = fread(buffer, sizeof(buffer), 1, file);
    if(res < 0){
        qlog_e("Md5Data data error!");
        return 2;
    }
    res = (unsigned char)(strchr(buffer, ' ') - buffer);
    char *ptr = (res > 0)?strndup(buffer, res):strdup(buffer);
    qlog_i("Md5Data value of res is:%d", res);
    fclose(file);
    strcpy(data,ptr);
    return 0;
}

static int getOldLink(const char* linkPath,char* data, int dataSize)
{
    char m_oldbuf[256];
    ssize_t sz;
    memset(m_oldbuf,0,sizeof(m_oldbuf));
    snprintf(m_oldbuf,sizeof(m_oldbuf),"readlink -f %s",linkPath);
    mySystem(m_oldbuf,data,dataSize);
    
    return 0;
}

static int setNewLink(const char* newTarger ,const char* linkPath)
{
    char m_cmd[100];
    memset(m_cmd,0,sizeof(m_cmd));
    snprintf(m_cmd,sizeof(m_cmd),"ln -snf %s %s",newTarger,linkPath);
    return system(m_cmd);
}

static int createNewLink(const char* target, const char* link, const char* path)
{
    char buf[100];
    ssize_t sz;
    DIR *dp;
    int fd;
 
    //获取相对路径名返回的文件描述符fd
    if((dp = opendir(path)) == NULL){
        qlog_e("opendir");
        return -1;
    }
    if((fd = dirfd(dp)) < 0){
        qlog_e("dirfd");
        return -1;
    }
 
    //创建符号链接
    if(symlinkat(target,fd,link) < 0){
        qlog_e("symlinkat");
        return -1;
    }
 
    //读取符号链接
    memset(buf,0,sizeof(buf));
    if((sz = readlinkat(fd,link,buf,sizeof(buf))) < 0){
        perror("readlinkat");
        return -1;
    }
 
    printf("buf:%s,length:%u\n",buf,(unsigned int)sz);
    close(fd);
    return 0;
}

size_t maxSize(void)
{
    return g_usMaxSize;
}

int32_t setMaxSize(size_t size)
{
    (size > 0)?g_usMaxSize = size:g_usMaxSize = (50*1024*1024);
    return 0;
}

const char *mountPoint(void)
{
    const char *ret = g_ucMountPoint;
    return ret;
}

int32_t setMountPoint(const char *path)
{
    if(path != NULL){
        char mkdir[100] = { '\0' };
        strcpy(g_ucMountPoint,path);
        snprintf(mkdir,sizeof(mkdir),"mkdir -p %s",path);
        int res = system(mkdir);
        if(res < 0){
            qlog_e("Wings mkdir failed");
        }
        return 0;
    }
    return -1;
}

bool mounted(void)
{
    int res = 0;
    if(strlen(g_ucMountPoint) > 0 && g_usMaxSize > 0){
        memset(g_ucMountCmd,'\0',sizeof(g_ucMountCmd));
        snprintf(g_ucMountCmd,sizeof(g_ucMountCmd),"mount -t ramfs -o size=%d ramfs %s",g_usMaxSize,g_ucMountPoint);
        res = system(g_ucMountCmd);
        if(res < 0){
            return false;
        }
        return true;
    }

    return false;
}

int32_t mount(void)
{
    int res = 0;
    if(umount() > 0){
        memset(g_ucMountCmd,'\0',sizeof(g_ucMountCmd));
        snprintf(g_ucMountCmd,sizeof(g_ucMountCmd),"mount -t ramfs -o size=%d ramfs %s",g_usMaxSize,g_ucMountPoint);
        return system(g_ucMountCmd);
    }else{
        return -1;
    }
}

int32_t umount(void)
{
    if(strlen(g_ucMountPoint) > 0)
    {
        memset(g_ucUmount,'\0',sizeof(g_ucUmount));
        snprintf(g_ucUmount,sizeof(g_ucUmount),"umount %s",g_ucMountPoint); 
        return system(g_ucUmount);//umount
    }
    return -1;
}

/**
 * @description: 设置文件解压路径
 * @param {char} *filePath
 * @return {*}
 */
int32_t setFilePath(const char *filePath)
{
    if(filePath != NULL){
        strcpy(g_ucFilePath,filePath);
        return 0;
    }
    return -1;
}


// 模式		描述
// "r"			打开一个用于读取的文件。该文件必须存在。
// "w"			创建一个用于写入的空文件。如果文件名称与已存在的文件相同，则会删除已有文件的内容，文件被视为一个新的空文件。
// "a"			追加到一个文件。写操作向文件末尾追加数据。如果文件不存在，则创建文件。
// "r+"		打开一个用于更新的文件，可读取也可写入。该文件必须存在。
// "w+"		创建一个用于读写的空文件。
// "a+"		打开一个用于读取和追加的文件。

void setConfiguration(const char *fileName)
{
    if(fileName != NULL){
        strcpy(g_ucConfName,fileName);
    }

}


/**
 * @description: 解压二进制流
 * @param {uint8_t} *src
 * @param {size_t} size
 * @param {char} *fileName
 * @return {*} 0 true -1 false
 */
int32_t decompressBinary(const uint8_t *src, size_t size, const char *fileName)
{
    if(fileName == NULL || src == NULL || size <= 0){
        qlog_e("decompressBinary parameters error !");
        return -1;
    }
    int res = 0,m_result = 0;
    char m_tarCmd[100] = { '\0' };
    char m_md5Cmd[100] = { '\0' };
    char m_TargetPackage[100] = {'\0'};
    char m_PathBuffer[100] = { '\0' };
    char m_TargetUpdate[100] = { '\0' };
    char m_SrcMd5[50] = { '\0' };
    char m_DstMd5[50] = { '\0' };
    char m_oldMd5[50] = { '\0' };
    /****************解出更新包包名************************/
    // char *p = strchr(m_TargetUpdate, '.');//p = '.'之后的字符串
    unsigned char tmp = (unsigned char)(strchr(fileName, '.') - fileName);
    char *ptr = (tmp > 0)?strndup(fileName, tmp):strdup(fileName);//ptr = update '.'之前的字符串
    
    /*****************************1、保存数据流到tar包***********************************/
    if(g_ucFilePath[0] == '\0'){
        qlog_e("decompressBinary g_ucFilePath is NULL");
        return -1;
    }

    strcpy(m_TargetUpdate,g_ucFilePath);
    strcat(m_TargetUpdate,"/");//获取解压后文件夹的路径
    strcat(m_TargetUpdate,fileName);//获取解压后文件夹的路径update.tar.bz2

    FILE* fd = fopen(m_TargetUpdate,"w+");
    if(fd == NULL){
        qlog_e("fopen failed %s",m_TargetUpdate);
        return -1;
    }
    if(fwrite(src ,size ,1 ,fd) == 1){
        qlog_i("write %s finished",m_TargetUpdate);
        fseek(fd,0,SEEK_SET);      //将文件读写指针偏移到文件头
        fclose(fd);    //关闭文件
    }else{
        qlog_e("write %s failed",m_TargetUpdate);
        fclose(fd);    //关闭文件
        return -1;
    }
    /*************************************************************************************/

    /*****************************2、解压fileNameBuffer(update.tar.bz2)包验证md5***********************************/
    //*****解压出来后没有文件夹****//
    snprintf(m_tarCmd,sizeof(m_tarCmd),"tar -vxf %s -C %s",m_TargetUpdate,g_ucFilePath);
    res = system(m_tarCmd);
    if(res < 0){
        qlog_e("%s failed",m_tarCmd);
        return -1;
    }
    //读取配置文件
    memset(m_PathBuffer,'\0',sizeof(m_PathBuffer));
    strcpy(m_PathBuffer,g_ucFilePath);
    strcat(m_PathBuffer,"/");
    strcat(m_PathBuffer,g_ucConfName);

    Description::loadDescription(m_PathBuffer);//读取更新配置

    auto Pack = Description::currentDescribe()->m_PackName + ".tar.bz2";
    strcpy(m_TargetPackage,g_ucFilePath);//package.tar.bz2
    strcat(m_TargetPackage,"/");
    strcat(m_TargetPackage,Pack.c_str());

    //校验md5
    snprintf(m_md5Cmd,sizeof(m_md5Cmd),"md5sum %s > %s/Md5to.txt",m_TargetPackage,g_ucFilePath);
    res = system(m_md5Cmd);
    if(res < 0){
        qlog_e("%s failed",m_md5Cmd);
        return -1;
    }
    //打开MD5源件
    memset(m_PathBuffer,'\0',sizeof(m_PathBuffer));
    strcpy(m_PathBuffer,g_ucFilePath);
    strcat(m_PathBuffer,MD5_NAME);

    res = getMd5Data(m_PathBuffer,m_SrcMd5);
    if(res != 0){
        qlog_e(" %s not found md5",m_PathBuffer);
        return -1;
    }
    /***************************************************/
    //打开MD5备份
    memset(m_PathBuffer,'\0',sizeof(m_PathBuffer));
    strcpy(m_PathBuffer,g_ucFilePath);
    strcat(m_PathBuffer,MD5_COPYNAME);

    res = getMd5Data(m_PathBuffer,m_DstMd5);
    if(res != 0){
        qlog_e(" %s not found md5",m_PathBuffer);
        return -1;
    }
    /****************************************************/

    /*******************分析更新包的配置文件*************/
    if (strcmp(m_DstMd5,m_SrcMd5) == 0) {//校验成功
        char oldFullPath[100];
        memset(oldFullPath,'/0',sizeof(oldFullPath));
        getOldLink((Description::currentDescribe()->m_link.uniqueSymbolLink).c_str(),oldFullPath,sizeof(oldFullPath));
        strcat(oldFullPath,MD5_NAME);//获取app绝对路径
        
        memset(m_oldMd5,'/0',sizeof(m_oldMd5));
        res = getMd5Data(oldFullPath,m_oldMd5);
        qlog_i("******md5 check ...!******");
        switch (res)//获取程序内的MD5,只要不为负数则可判断数据读取成功
        {
        case 0:m_result = (strcmp(m_oldMd5,m_SrcMd5) == 0)?-1:0;
            break;
        case 1:m_result = 0;
            break;
        case 2:m_result = 0;
            break;
        
        default:m_result = -1;
            break;
        }
        if (m_result == 0) {
            qlog_i("******md5 check success!******");
        } else {
            qlog_i("******md5 check failed!******");
        }
        return m_result;
    } else {
        qlog_e("******md5 check failed!******");
        return -1;
    }
    /*************************************************************************************/
    
}

int32_t updatePackage(void)
{
    int res = 0;
    char m_Cmd[100] = { '\0' };
    char m_PathBuffer[100] = { '\0' };
    char m_TargetPackage[100] = { '\0' };
    char m_oldPath[100] = { '\0' };
    /********************3、解压对应数据包到更新文件夹******************************/

    auto m_packName = Description::currentDescribe()->m_PackName.c_str();//解压package.tar.bz2更新包
    memset(m_PathBuffer,'\0',sizeof(m_PathBuffer));
    strcpy(m_PathBuffer,g_ucFilePath);
    strcat(m_PathBuffer,std::string("/" + Description::currentDescribe()->m_PackName +".tar.bz2").c_str());
    

    //tar -vxf home/admin/ram/package.tar.bz2 -C /home/admin/ram
    memset(m_Cmd,'\0',sizeof(m_Cmd));
    snprintf(m_Cmd,sizeof(m_Cmd),"tar -vxf %s -C %s",m_PathBuffer,g_ucFilePath);
    res = system(m_Cmd);
    if(res < 0){
        qlog_e("%s failed",m_Cmd);
        return -1;
    }
    
    /****************************************************************************/
    
    /********************4.获取要更新的路径后 判断是否需要覆盖后再强制覆盖到指定位置************************/
    ///home/admin/bin/ping or /home/admin/bin/pong
    getOldLink((Description::currentDescribe()->m_link.uniqueSymbolLink).c_str(),m_oldPath,sizeof(m_oldPath));
    auto strPing = (Description::currentDescribe()->m_link.pingBank).c_str();
    auto newPath = (strcmp(m_oldPath,strPing) == 0)?
    (Description::currentDescribe()->m_link.pongBank):(Description::currentDescribe()->m_link.pingBank);
    

    
    
    if(Description::currentDescribe()->m_app.directory > 0){//更新app
        qlog_i("wing start update %s",(Description::currentDescribe()->m_app.src).c_str());
        updateApp(newPath.c_str());
    }

    if(Description::currentDescribe()->m_driver.directory > 0){//更新驱动
        qlog_i("wing start update %s",(Description::currentDescribe()->m_driver.src).c_str());
        updateDriver(newPath.c_str());
    }

    if(Description::currentDescribe()->m_other.directory > 0){//更新杂项
        qlog_i("wing start update %s",(Description::currentDescribe()->m_other.src).c_str());
        updateOther(newPath.c_str());
    }
    /*************************************最后修改软连接************************************************/
    auto linkBank = (Description::currentDescribe()->m_link.uniqueSymbolLink).c_str();
    setNewLink(newPath.c_str() ,linkBank);
    /*************************************最后修改软连接************************************************/

    /***********************************更新完毕后为此次更新加上MD5.txt 校验******************************/

    strcpy(m_TargetPackage,g_ucFilePath);//package.tar.bz2
    strcat(m_TargetPackage,std::string("/" + Description::currentDescribe()->m_PackName + ".tar.bz2").c_str());

    //生成md5
    memset(m_Cmd,'\0',sizeof(m_Cmd));
    snprintf(m_Cmd,sizeof(m_Cmd),"md5sum %s > %s/Md5.txt",m_TargetPackage,newPath.c_str());
    res = system(m_Cmd);
    if(res < 0){
        qlog_e("%s failed",m_Cmd);
        return -1;
    }
    return 0;
}

static void updateApp(const char *new_path)
{
    int res = 0;
    char m_Buffer[100] = { '\0' };
    char m_Cmd[256] = { '\0' };
    memset(m_Buffer,'\0',sizeof(m_Buffer));
    strcpy(m_Buffer,g_ucFilePath);
    auto path = std::string("/") + Description::currentDescribe()->m_PackName 
                + std::string("/") + Description::currentDescribe()->m_app.src;
    strcat(m_Buffer,path.c_str());

    memset(m_Cmd,'\0',sizeof(m_Cmd));
    snprintf(m_Cmd,sizeof(m_Cmd),"cp %s %s -rf",m_Buffer,new_path);
    res = system(m_Cmd);
    if(res < 0){
        qlog_e("%s failed",m_Cmd);
        return;
    }
    qlog_i("wings finished APP update %s",m_Buffer);
}

static void updateDriver(const char *new_path)
{
    int res = 0;
    char m_Buffer[100] = { '\0' };
    char m_Cmd[256] = { '\0' };
    memset(m_Buffer,'\0',sizeof(m_Buffer));
    strcpy(m_Buffer,g_ucFilePath);
    auto path = std::string("/") + Description::currentDescribe()->m_PackName 
                + std::string("/") + Description::currentDescribe()->m_driver.src;
    strcat(m_Buffer,path.c_str());

    memset(m_Cmd,'\0',sizeof(m_Cmd));
    snprintf(m_Cmd,sizeof(m_Cmd),"cp %s %s -rf",m_Buffer,new_path);
    res = system(m_Cmd);
    if(res < 0){
        qlog_e("%s failed",m_Cmd);
        return;
    }
    qlog_i("wings finished Driver update %s",m_Buffer);
}

static void updateOther(const char *new_path)
{
    int res = 0;
    char m_Buffer[100] = { '\0' };
    char m_Cmd[256] = { '\0' };
    memset(m_Buffer,'\0',sizeof(m_Buffer));
    strcpy(m_Buffer,g_ucFilePath);
    auto path = std::string("/") + Description::currentDescribe()->m_PackName 
                + std::string("/") + Description::currentDescribe()->m_other.src;
    strcat(m_Buffer,path.c_str());

    memset(m_Cmd,'\0',sizeof(m_Cmd));
    snprintf(m_Cmd,sizeof(m_Cmd),"cp %s %s -rf",m_Buffer,new_path);
    res = system(m_Cmd);
    if(res < 0){
        qlog_e("%s failed",m_Cmd);
        return;
    }
    qlog_i("wings finished Other update %s",m_Buffer);
}

uint32_t majorVer(void)
{
    return g_usMajor;
}

uint32_t minorVer(void)
{
    return g_usMinor;
}

uint32_t patchVer(void)
{
    return g_usPatch;
}
}
